package bancosys.tec.services.input;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

import jmine.tec.di.injector.Injector;
import jmine.tec.di.metadata.DependencyMetaData;
import jmine.tec.di.metadata.impl.DependencyMetaDataImpl;
import jmine.tec.di.resolver.impl.DynamicObjectResolver;
import jmine.tec.di.scope.TypedScope;
import jmine.tec.di.type.NoTypeConverter;
import jmine.tec.di.type.TypeConvertionException;
import jmine.tec.di.validation.StaticValidator;
import jmine.tec.di.wrapper.NoWrapper;
import jmine.tec.services.ServicesController;
import jmine.tec.services.annotations.ServiceFields;
import jmine.tec.services.annotations.ServiceImplementor;
import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.api.Service;
import jmine.tec.services.api.ServiceExecutionException;
import jmine.tec.services.api.ServicesAPIMessages;
import jmine.tec.services.api.factory.ServiceCreationException;
import jmine.tec.services.api.factory.ServiceFactory;
import jmine.tec.services.api.factory.ServiceNotFoundException;
import jmine.tec.services.export.impl.ExportUtils;
import jmine.tec.services.impl.ServicesImplMessages;
import jmine.tec.utils.Tuple;
import jmine.tec.utils.collection.impl.CopyOnWriteHashMap;
import jmine.tec.utils.reflection.AnnotationUtils;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.SpringTransactionAnnotationParser;

import bancosys.tec.component.ControllerLocator;
import bancosys.tec.persist.controller.PersistenceEnabledController;
import bancosys.tec.services.api.AbstractService;
import bancosys.tec.services.implementor.ServiceImplementorAccessor;
import bancosys.tec.services.implementor.ServiceImplementorProxy;
import bancosys.tec.services.implementor.ServiceImplementorRuntime;
import bancosys.tec.services.implementor.UnmodifiableServiceImplementorAccessor;
import bancosys.tec.services.implementor.descriptor.InstanceCreator;
import bancosys.tec.services.implementor.descriptor.ServiceExecutionProxy;
import bancosys.tec.services.implementor.descriptor.ServiceOutputExtractor;
import bancosys.tec.services.implementor.descriptor.ServiceRuntimeImpl;
import bancosys.tec.services.implementor.descriptor.ServiceValidator;

/**
 * Fabrica padrao de servicos.
 * 
 * @author Rafael Volpato (Jun 4, 2007)
 */
public class DefaultServiceFactory implements ServiceFactory, BeanFactoryAware {

    private static final Logger LOG = Logger.getLogger(ServiceFactory.class);

    private final ControllerLocator controllerLocator;

    private final ServicesController servicesController;

    private final Map<String, ServiceAccessorReference> registeredServices = new ConcurrentHashMap<String, ServiceAccessorReference>();

    private final Map<Class<?>, List<Injector>> cachedInjectors = new CopyOnWriteHashMap<Class<?>, List<Injector>>();

    private final Map<Class<?>, String> registeredInsertServices = new ConcurrentHashMap<Class<?>, String>();

    private final Map<String, Class<? extends Service<?>>> registeredHierarquicalServices =
            new ConcurrentHashMap<String, Class<? extends Service<?>>>();

    private ServiceImplementorRuntime serviceRuntime;

    /**
     * Construtor padrão.
     * 
     * @param servicesController servicesController
     * @param controllerLocator controllerLocator
     */
    public DefaultServiceFactory(ServicesController servicesController, ControllerLocator controllerLocator) {
        this(servicesController, controllerLocator, new ServiceRuntimeImpl(servicesController, controllerLocator,
                servicesController.getDAOFactory()));
    }

    /**
     * Construtor padrão.
     * 
     * @param servicesController servicesController
     * @param controllerLocator controllerLocator
     * @param decriptor o {@link ServiceImplementorRuntime}
     */
    public DefaultServiceFactory(ServicesController servicesController, ControllerLocator controllerLocator,
            ServiceImplementorRuntime decriptor) {
        this.servicesController = servicesController;
        this.controllerLocator = controllerLocator;
        this.serviceRuntime = decriptor;
    }

    /**
     * Retorna uma nova instancia do servico desejado com as serviceProperties e controller injetados.
     * 
     * @param action acao
     * @param serviceName nome do servico
     * @param serviceProperties propriedades do servico
     * @return uma instancia do servico.
     * @throws ServiceCreationException Se o servico não for encontrado ou se não for possivel instanciar o servico.
     */
    public Service<? extends PersistenceEnabledController> getService(ActionsEnum action, String serviceName, TypedScope serviceProperties)
            throws ServiceCreationException {
        Service<? extends PersistenceEnabledController> service = this.instantiateService(action, serviceName);
        return this.initializeService(serviceProperties, service);
    }

    /**
     * Inicializa as dependencias dos servicos
     * 
     * @param serviceProperties the {@link TypedScope}
     * @param service {@link Service}
     * @return {@link Service}
     */
    private Service<? extends PersistenceEnabledController> initializeService(TypedScope serviceProperties,
            Service<? extends PersistenceEnabledController> service) {
        // Setar as propriedades no servico
        service.setServiceProperties(serviceProperties);

        // Setar o controller
        this.injetarController(service);
        return service;
    }

    /**
     * {@inheritDoc}
     */
    public Service<? extends PersistenceEnabledController> getService(Class<?> targetClass, TypedScope typedScope)
            throws ServiceCreationException {
        String serviceName = this.registeredInsertServices.get(targetClass);
        if (serviceName == null) {
            ServiceImplementorAccessor accessor = this.parseUntil(targetClass);
            if (accessor == null) {
                throw new ServiceNotFoundException(ServicesImplMessages.SERVICE_NOT_IMPLEMENTED_FOR_CLASS.create(targetClass.getName()));
            }
            Service<? extends PersistenceEnabledController> service = this.createServiceFor(accessor);
            return this.initializeService(typedScope, service);
        }
        return this.getService(ActionsEnum.INCLUIR, serviceName, typedScope);
    }

    /**
     * Eagerly intialize services until the desired service is initialized
     * 
     * @param targetClass the target class
     * @throws ServiceCreationException the exception
     * @return {@link ServiceImplementorAccessor} or null if none is found
     */
    private ServiceImplementorAccessor parseUntil(Class<?> targetClass) throws ServiceCreationException {
        for (ServiceAccessorReference ref : this.registeredServices.values()) {
            ServiceImplementorAccessor accessor = ref.get();
            Map<String, Class<?>> outputTypes = accessor.getOutputExtractor().getOutputTypes();
            if (accessor.getActionType().incluir() && outputTypes.size() == 1 && outputTypes.values().iterator().next().equals(targetClass)) {
                return accessor;
            }
        }
        return null;
    }

    /**
     * Verifica se o servico precisa ter dependencias injetadas. Se sim, faz um pequeno wrapper em volta do servico que injeta as
     * dependencias um pouco antes de ser executado.
     * 
     * @param service {@link Service}
     * @return {@link Service}
     */
    private Service<? extends PersistenceEnabledController> wrapIfNecessary(Service<? extends PersistenceEnabledController> service) {
        final List<Injector> injectors = this.getOrCreateInjectors(service);
        if (!injectors.isEmpty()) {
            // must write a small wrapper
            return new InjectingDelegateService(service, injectors);
        }
        return service;
    }

    /**
     * Pega os injectores do cache ou faz o parse da classe para pegar injetores
     * 
     * @param service Service
     * @return List de Injetctor
     */
    private List<Injector> getOrCreateInjectors(Service<? extends PersistenceEnabledController> service) {
        List<Injector> list = this.cachedInjectors.get(service.getClass());
        if (list != null) {
            return list;
        }
        final List<Injector> injectors = this.serviceRuntime.getDependencyDecriber().createInjectorsFor(service.getClass());
        this.cachedInjectors.put(service.getClass(), injectors);
        return injectors;
    }

    /**
     * Injeta o controller no serviço.
     * 
     * @param <C> controller principal do serviço
     * @param service serviço
     */
    @SuppressWarnings({ "unchecked", "deprecation" })
    private <C extends PersistenceEnabledController> void injetarController(Service<C> service) {
        Class<C> controllerClass = service.getControllerClass();
        if (controllerClass != null) {
            C controller = (C) this.controllerLocator.findControllerForClass(controllerClass);
            service.setController(controller);
        }
        service.setServicesController(this.servicesController);
    }

    /**
     * Busca o serviço no classpath e instancia o serviço.
     * <p>
     * Este método lida com as duas API's de construção de serviços (anotados e hierárquicos).
     * 
     * @param action action
     * @param serviceName serviceName
     * @return instancia do serviço
     * @throws ServiceCreationException se ocorrer algum erro.
     */
    @SuppressWarnings("unchecked")
    protected Service<? extends PersistenceEnabledController> instantiateService(ActionsEnum action, String serviceName)
            throws ServiceCreationException {
        final String cacheKey = this.cacheKeyForService(action, serviceName);

        // procura por um serviço da nova API previamente acessado / registrado
        ServiceAccessorReference serviceImplementorAccessor = this.registeredServices.get(cacheKey);
        if (serviceImplementorAccessor != null) {
            return this.createServiceFor(serviceImplementorAccessor.get());
        }

        // senão procura por um serviço na API anterior previamente acessado / registrado
        Class serviceClazz = this.registeredHierarquicalServices.get(cacheKey);
        if (serviceClazz != null) {
            return this.wrapIfNecessary(this.createServiceFor(serviceClazz));
        }
        throw new ServiceNotFoundException(ServicesImplMessages.SERVICE_NOT_IMPLEMENTED.create(action, serviceName));
    }

    /**
     * Devolve o nome da chave a ser usado no mapa de caches
     * 
     * @param action a {@link ActionsEnum}
     * @param serviceName o nome do service
     * @return {@link String}
     */
    private String cacheKeyForService(ActionsEnum action, String serviceName) {
        return action.name() + "|" + serviceName;
    }

    /**
     * Cria um servico a partir de um {@link ServiceImplementorAccessor}
     * 
     * @param serviceImplementorAccessor o {@link ServiceImplementorAccessor}
     * @return {@link Service}
     */
    private Service<? extends PersistenceEnabledController> createServiceFor(ServiceImplementorAccessor serviceImplementorAccessor) {
        return new ServiceImplementorProxy(serviceImplementorAccessor);
    }

    /**
     * Cria um servico a partir de um {@link Class}
     * 
     * @param serviceClass A classe do serviço
     * @return {@link Service}
     * @throws ServiceCreationException Se não conseguir criar o serviço
     */
    private Service<? extends PersistenceEnabledController> createServiceFor(
            Class<? extends Service<? extends PersistenceEnabledController>> serviceClass) throws ServiceCreationException {
        try {
            return serviceClass.newInstance();
        } catch (InstantiationException e) {
            throw new ServiceCreationException(ServicesAPIMessages.FACTORY_INSTANTIATION_ERROR.create(ExportUtils
                    .getServiceName(serviceClass)), e);
        } catch (IllegalAccessException e) {
            throw new ServiceCreationException(ServicesAPIMessages.FACTORY_ILLEGAL_ACCESS_ERROR.create(ExportUtils
                    .getServiceName(serviceClass)), e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Set<String> getServices() {
        Set<String> paths = new TreeSet<String>();

        paths.addAll(this.registeredServices.keySet());
        paths.addAll(this.registeredHierarquicalServices.keySet());

        return paths;
    }

    /**
     * @param serviceRuntime the serviceRuntime to set
     */
    public void setServiceRuntime(ServiceImplementorRuntime serviceRuntime) {
        this.serviceRuntime = serviceRuntime;
    }

    /**
     * @param types os tipos
     * @throws ServiceCreationException Se alguma classe for inválida
     */
    @SuppressWarnings("unchecked")
    public void setRegisteredServices(Class<?>... types) throws ServiceCreationException {
        for (Class<?> clazz : types) {
            Tuple<Class<?>, ServiceImplementor> tuple = AnnotationUtils.findTypeAndAnnotation(clazz, ServiceImplementor.class);
            if (tuple != null) {
                // API nova
                String serviceName = tuple.tail().serviceName();
                if (serviceName.length() == 0) {
                    serviceName = AbstractService.getServiceName(clazz);
                }
                ActionsEnum action = tuple.tail().action();
                String keyForService = this.cacheKeyForService(action, serviceName);
                this.registeredServices.put(keyForService, new ServiceAccessorReferenceImpl(new ServiceParserCallable(clazz)));
            } else {
                // API anterior
                if (Service.class.isAssignableFrom(clazz)) {
                    String serviceName = AbstractService.getServiceName(clazz);
                    ActionsEnum action;
                    Service<?> s;
                    try {
                        s = (Service<?>) clazz.newInstance();
                        action = s.getAction();
                    } catch (InstantiationException e) {
                        throw new ServiceCreationException(ServicesAPIMessages.FACTORY_INSTANTIATION_ERROR.create(serviceName), e);
                    } catch (IllegalAccessException e) {
                        throw new ServiceCreationException(ServicesAPIMessages.FACTORY_ILLEGAL_ACCESS_ERROR.create(serviceName), e);
                    }

                    this.registeredHierarquicalServices.put(this.cacheKeyForService(action, serviceName),
                            (Class<? extends Service<?>>) s.getClass());
                }
            }
        }
    }

    /**
     * @param beanFactory the baen factory
     */
    public void setBeanFactory(BeanFactory beanFactory) {
        this.serviceRuntime.setBeanFactory(beanFactory);
    }

    /**
     * {@inheritDoc}
     */
    public void register(Collection<? extends Class<?>> registers) {
        if (registers != null) {
            Class<?>[] types = registers.toArray(new Class<?>[registers.size()]);
            try {
                this.setRegisteredServices(types);
            } catch (ServiceCreationException e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    }

    /**
     * Devolve as referencias para os servicos novos implementados. Modificacoes nesta lista nao refletem servicos disponiveis. A lista nao
     * esta em nenhuma ordem particular.
     * 
     * @return {@link List} de {@link ServiceImplementorAccessor}
     * @deprecated usar {@link #getServicesDefinition()} que devolve todos os servicos - tanto os novos quanto os velhos
     */
    @Deprecated
    public List<ServiceImplementorAccessor> getServiceAccessors() {
        Collection<ServiceAccessorReference> values = this.registeredServices.values();
        List<ServiceImplementorAccessor> list = new ArrayList<ServiceImplementorAccessor>(values.size());
        for (ServiceAccessorReference serviceAccessorReference : values) {
            try {
                list.add(serviceAccessorReference.get());
            } catch (ServiceCreationException e) {
                // ignore
            }
        }
        return list;
    }

    /**
     * Adapter para adaptar um servico da infra antiga para a interface {@link ServiceImplementorAccessor}. Praticamente todos os metodos
     * sao funcionais, exceto {@link #executeAndFillScope(Object, TypedScope)}
     * 
     * @author takeshi
     */
    private static final class ServiceFieldsAdapter implements ServiceImplementorAccessor {
        private final Class<?> oldServiceClass;

        private final ServiceFields fields;

        private final ServiceFactory serviceFactory;

        /**
         * Construtor
         * 
         * @param oldServiceClass {@link Class}
         * @param serviceFactory {@link ServiceFactory}
         */
        private ServiceFieldsAdapter(Class<?> oldServiceClass, ServiceFactory serviceFactory) {
            this.oldServiceClass = oldServiceClass;
            this.serviceFactory = serviceFactory;
            this.fields = AnnotationUtils.findClassAndAnnotationWithAnnotation(oldServiceClass, ServiceFields.class).getTail();
        }

        /**
         * {@inheritDoc}
         */
        public List<String> validateStaticState(TypedScope scope) {
            final List<DependencyMetaData> allDependencies = this.getAllDependencies();
            Collections.sort(allDependencies, new UnmodifiableServiceImplementorAccessor.DependencyMetaDataComparator());
            return StaticValidator.validateSimple(scope, allDependencies);
        }

        /**
         * {@inheritDoc}
         */
        public void validateBeforeExecution(Object target, TypedScope scope) throws ServiceExecutionException {
        }

        /**
         * {@inheritDoc}
         */
        public boolean isStateless() {
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public ServiceValidator getValidator() {
            return new ServiceValidator() {

                public void validateThrowException(Object target, TypedScope scope) throws ServiceExecutionException {
                }

                public List<String> validate(Object target, TypedScope properties) {
                    return Collections.emptyList();
                }

                public List<Method> getValidationMethods() {
                    return Collections.emptyList();
                }
            };
        }

        /**
         * {@inheritDoc}
         */
        public TransactionDefinition getTransactionDefinition() {
            return new SpringTransactionAnnotationParser().parseTransactionAnnotation(this.oldServiceClass);
        }

        /**
         * {@inheritDoc}
         */
        public String getServiceName() {
            return AbstractService.getServiceName(this.oldServiceClass);
        }

        /**
         * {@inheritDoc}
         */
        public ServiceExecutionProxy getServiceExecutionProxy() {
            throw new UnsupportedOperationException();
        }

        /**
         * {@inheritDoc}
         */
        public ServiceOutputExtractor getOutputExtractor() {
            return new ServiceOutputExtractor() {

                public Map<String, Class<?>> getOutputTypes() {
                    return new HashMap<String, Class<?>>();
                }

                public Map<String, Object> getOutput(Object target, TypedScope scope) {
                    return new HashMap<String, Object>();
                }
            };
        }

        /**
         * {@inheritDoc}
         */
        public Class<?> getImplementorType() {
            return this.oldServiceClass;
        }

        /**
         * {@inheritDoc}
         */
        public ServiceOutputExtractor getExtractor() {
            return this.getOutputExtractor();
        }

        /**
         * {@inheritDoc}
         */
        public InstanceCreator getCreator() {
            return new InstanceCreator() {

                public List<String> validateRequiredProperties(TypedScope properties) {
                    return Collections.emptyList();
                }

                public Object hydrateAndCreateInstance(TypedScope properties) throws TypeConvertionException {
                    try {
                        return ServiceFieldsAdapter.this.serviceFactory.getService(ServiceFieldsAdapter.this.getActionType(),
                                ServiceFieldsAdapter.this.getServiceName(), properties);
                    } catch (ServiceCreationException e) {
                        throw new RuntimeException(e);
                    }
                }

                public List<DependencyMetaData> getDependencies() {
                    return Collections.emptyList();
                }
            };
        }

        /**
         * {@inheritDoc}
         */
        public List<DependencyMetaData> getAllDependencies() {
            List<DependencyMetaData> list =
                    new ArrayList<DependencyMetaData>(this.fields.required().length + this.fields.optional().length);
            for (String depName : this.fields.required()) {
                list.add(new DependencyMetaDataImpl(depName, new DynamicObjectResolver(Object.class, depName,
                        NoTypeConverter.getInstance(), null), true, new NoWrapper(), Object.class));
            }
            for (String depName : this.fields.optional()) {
                list.add(new DependencyMetaDataImpl(depName, new DynamicObjectResolver(Object.class, depName,
                        NoTypeConverter.getInstance(), null), false, new NoWrapper(), Object.class));
            }
            return list;
        }

        /**
         * {@inheritDoc}
         */
        public ActionsEnum getActionType() {
            return this.fields.action();
        }

        /**
         * {@inheritDoc}
         */
        public void executeAndFillScope(Object service, TypedScope scope) throws Exception {
            ((Service<?>) service).execute();
        }

        /**
         * {@inheritDoc}
         */
        public Object createWithDependencies(TypedScope scope) throws TypeConvertionException {
            return this.getCreator().hydrateAndCreateInstance(scope);
        }
    }

    /**
     * {@link ServiceAccessorReference} que usa um RunnableFuture para pegar as entidades
     * 
     * @author takeshi
     */
    private static class ServiceAccessorReferenceImpl implements ServiceAccessorReference {

        private final FutureTask<ServiceImplementorAccessor> task;

        /**
         * C'tor
         * 
         * @param callable {@link Callable}
         */
        public ServiceAccessorReferenceImpl(Callable<ServiceImplementorAccessor> callable) {
            this.task = new FutureTask<ServiceImplementorAccessor>(callable);
        }

        /**
         * {@inheritDoc}
         */
        public ServiceImplementorAccessor get() throws ServiceCreationException {
            this.task.run();
            try {
                return this.task.get();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IllegalStateException("Thread was cancelled", e);
            } catch (ExecutionException e) {
                if (e.getCause() instanceof RuntimeException) {
                    throw (RuntimeException) e.getCause();
                } else if (e.getCause() instanceof Error) {
                    throw (Error) e.getCause();
                }
                if (e.getCause() instanceof ServiceCreationException) {
                    throw (ServiceCreationException) e.getCause();
                } else {
                    throw new ServiceCreationException(ServicesAPIMessages.FACTORY_INSTANTIATION_ERROR.create(), e);
                }
            }
        }

    }

    /**
     * {@link Callable} que faz o parse do servico passado. Serve apenas para os servicos da infra nova.
     * 
     * @author takeshi
     */
    private class ServiceParserCallable implements Callable<ServiceImplementorAccessor> {

        private final Class<?> referenceClass;

        /**
         * C'tor
         * 
         * @param referenceClass the {@link Class}
         */
        public ServiceParserCallable(Class<?> referenceClass) {
            super();
            this.referenceClass = referenceClass;
        }

        /**
         * {@inheritDoc}
         * 
         * @see java.util.concurrent.Callable#call()
         */
        public ServiceImplementorAccessor call() throws Exception {
            ServiceImplementorAccessor accessor = DefaultServiceFactory.this.serviceRuntime.describeService(this.referenceClass);
            String serviceName = accessor.getServiceName();

            // Armazena os servicos que possuem somente 1 output
            Map<String, Class<?>> outputTypes = accessor.getOutputExtractor().getOutputTypes();
            if (accessor.getActionType().incluir() && outputTypes.size() == 1) {
                for (Class<?> targetClass : outputTypes.values()) {
                    String registeredServiceName = DefaultServiceFactory.this.registeredInsertServices.get(targetClass);
                    if (registeredServiceName == null) {
                        DefaultServiceFactory.this.registeredInsertServices.put(targetClass, serviceName);
                    } else if (registeredServiceName.equals(serviceName)) {
                        LOG.warn("O serviço '" + serviceName + "' foi registrado mais de uma vez!");
                    } else {
                        throw new ServiceCreationException(ServicesImplMessages.CANNOT_CHANGE_SERVICE_FOR_CLASS.create(targetClass,
                                registeredServiceName, serviceName));
                    }
                }
            }
            return accessor;
        }

    }

    /**
     * Devolve todas as definicoes de servicos
     * 
     * @return {@link Collection} de {@link ServiceImplementorAccessor}
     */
    public Collection<? extends ServiceImplementorAccessor> getServicesDefinition() {
        List<ServiceImplementorAccessor> services = new ArrayList<ServiceImplementorAccessor>(this.registeredServices.size());
        for (ServiceAccessorReference reference : this.registeredServices.values()) {
            try {
                services.add(reference.get());
            } catch (ServiceCreationException e) {
                LOG.error("Cannot create service", e);
            }
        }
        for (Class<?> oldServiceClass : this.registeredHierarquicalServices.values()) {
            services.add(this.createServiceImplementorForOldService(oldServiceClass));
        }
        return services;
    }

    /**
     * Cria um {@link ServiceImplementorAccessor} para um servico anotado com {@link ServiceFields}
     * 
     * @param oldServiceClass Class
     * @return {@link ServiceImplementorAccessor}
     */
    private ServiceImplementorAccessor createServiceImplementorForOldService(final Class<?> oldServiceClass) {
        return new ServiceFieldsAdapter(oldServiceClass, this);
    }

    /**
     * Devolve um {@link ServiceImplementorAccessor} para um servico. Pode devolver um proxy funcional para servicos anotados com
     * {@link ServiceFields}
     * 
     * @param action {@link ActionsEnum}
     * @param serviceName {@link String}
     * @return {@link ServiceImplementorAccessor}
     */
    public ServiceImplementorAccessor getServiceAccessorFor(ActionsEnum action, String serviceName) {
        String cacheKeyForService = this.cacheKeyForService(action, serviceName);
        ServiceAccessorReference reference = this.registeredServices.get(cacheKeyForService);
        if (reference != null) {
            try {
                return reference.get();
            } catch (ServiceCreationException e) {
                LOG.error("Cannot create service", e);
            }
        }
        Class<? extends Service<?>> oldServiceClass = this.registeredHierarquicalServices.get(cacheKeyForService);
        if (oldServiceClass != null) {
            return this.createServiceImplementorForOldService(oldServiceClass);
        }
        return null;
    }

}
